/* Copyright (C) 2013

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
*/
package com.poz.plotview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.*;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.text.Layout.Alignment;
import android.util.AttributeSet;
import android.view.*;

import java.lang.Math;
import java.lang.Override;
import java.lang.String;
import java.security.InvalidParameterException;
import java.text.DecimalFormat;
import java.text.Format;
import java.util.ArrayList;
import java.util.List;

import com.poz.plotview.R;

/**
 * Custom view that shows a pie chart and, optionally, a label.
 */
public class PlotView extends View {
    private List<Seria> mData = new ArrayList<Seria>();
	private float xmin, xmax, ymin, ymax;
	private RectF mPlotBounds;
	private Paint mPlotPaint;
	private Path mPath = new Path();

	public float axisXSpacing = 35; // space for x label and text
	public float axisYSpacing = 35; // space for y label and ticks
	
	public float xTickTextHeight = 20;
	public float yTickTextHeight = 20;
	public int xTickColor = Color.BLACK;
	public int yTickColor = Color.BLACK;
	public Format xTickFormat = new DecimalFormat("##.0");
	public Format yTickFormat = new DecimalFormat("##.0");
	public float[] xTicks;
	public float[] yTicks;
	public int gridColor = Color.GRAY;
	
	public float labelTextHeight = 16;
	public String labelX = "";
	public String labelY = "";
	
	public static final PathEffect DASHED = new DashPathEffect(new float[] {10,20}, 0);
	public static final PathEffect SOLID = null;
	
	public static enum VALIGN {TOP, CENTER, BOTTOM};
	public static enum HALIGN {LEFT, CENTER, RIGHT};
	
	
    public PlotView(Context context) {
        super(context);
        init();
    }

    public PlotView(Context context, AttributeSet attrs) {
        super(context, attrs);
//        TypedArray a = context.getTheme().obtainStyledAttributes(
//                attrs,
//                R.styleable.PlotView,
//                0, 0
//        );
//
//        try {
////            mShowText = a.getBoolean(R.styleable.Plot_showText, false);
//        } finally {
//            // release the TypedArray so that it can be reused.
//            a.recycle();
//        }
        init();
    }
    
    public void clear() {
    	mData.clear();
    	update();
    }
    
    public void redraw() {
    	update();
    }
    
    /****************************************************************
     *  plot
     ****************************************************************/
    
    public int plot(float[] x, float[] y, String label, 
    		int color, int width, PathEffect effect) {
        Seria s = new Seria(x, y);
        s.mLabel = label;
        s.mColor = color;
        s.mWidth = width;
        s.mEffect = effect;
        mData.add(s);
        onDataChanged();
        return mData.size() - 1;
    }
    
    public int plot(float[] x, float[] y, int color) {
    	return plot(x, y,  "", color, 1, SOLID);
    }
    public int plot(float[] x, float[] y) {
    	return plot(x, y,  "", Color.BLACK, 1, SOLID);
    }
    public int plot(float[] x, float[] y, String label) {
    	return plot(x, y,  label, Color.BLACK, 1, SOLID);
    }
    public int plot(float[] x, float[] y, String label, int color) {
    	return plot(x, y, label, color, 1, SOLID);
    }
    public int plot(float[] x, float[] y, String label, int color, int width) {
    	return plot(x, y, label, color, width, SOLID);
    }
    
    /****************************************************************
     * add image seria
     ****************************************************************/
    
    public int bitmapScatter(float[] x, float[] y, Bitmap[] bm, String label, HALIGN ha, VALIGN va) {
    	Seria s = new Seria(x, y);
        s.mLabel = label;
        s.mWidth = 0; // we don't want stroke here
        if (bm.length != s.sz()) {
    		throw new InvalidParameterException("x and y must be with the same length");
    	}
        s.bms = bm;
        s.hAlign = ha;
        s.vAlign = va;
        
        mData.add(s);
        onDataChanged();
        return mData.size() - 1;
    }
    
    public int bitmapScatter(float[] x, float[] y, Bitmap bm, String label, HALIGN ha, VALIGN va) {
    	Bitmap[] bms = new Bitmap[x.length];
    	for (int i = 0; i < bms.length; i++) {
    		bms[i] = bm;
    	}
    	return bitmapScatter(x, y, bms, label, ha, va);
    }
    
    public int bitmapScatter(float[] x, float[] y, Bitmap bm, String label) {
    	return bitmapScatter(x, y, bm, label, HALIGN.CENTER, VALIGN.CENTER);
    }
    
    /****************************************************************
     *  put text in axis coordinates
     ****************************************************************/
    
    public int text(float[] x, float[] y, String[] texts, int color, HALIGN ha, VALIGN va ) {
    	Seria s = new Seria(x, y);
        s.mWidth = 0; // we don't want stroke here
        if (texts.length != s.sz()) {
    		throw new InvalidParameterException("x and y must be with the same length");
    	}
        s.mColor = color;
        s.texts = texts;
        s.hAlign = ha;
        s.vAlign = va;
        
        mData.add(s);
        onDataChanged();
        return mData.size() - 1;
 	}
    
    public int text(float[] x, float[] y, String[] texts) {
    	return text(x, y, texts, Color.BLACK, HALIGN.CENTER, VALIGN.BOTTOM);
 	}
    
    
    /****************************************************************
     *  put text in figure coordinates
     *  (0,0) is bottom left side.
     ****************************************************************/
    
 	private void textFigure(String txt, float x, float y, float deg, Canvas canvas) {
 		mPlotPaint.setStrokeWidth(0);
 		if (deg != 0) {
 			canvas.save();
 			canvas.rotate(deg, x, y);
 		}
     	canvas.drawText(txt, x, y, mPlotPaint);
     	if (deg != 0) {
     		canvas.restore();
     	}
 	}
 	
 	private void textFigure(String txt, float x, float y, Canvas canvas) {
 		textFigure(txt, x, y, 0, canvas);
 	}
 	
//    @Override
//    protected void onLayout(boolean changed, int l, int t, int r, int b) {
//        // Do nothing. Do not call the superclass method--that would start a layout pass
//        // on this view's children. Plot lays out its children in onSizeChanged().
//    }


//    //
//    // Measurement functions. This example uses a simple heuristic: it assumes that
//    // the pie chart should be at least as wide as its label.
//    //
//    @Override
//    protected int getSuggestedMinimumWidth() {
//        return (int) mTextWidth  * 2;
//    }
//
//    @Override
//    protected int getSuggestedMinimumHeight() {
//        return (int) mTextWidth;
//    }
//
//    @Override
//    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        // Try for a width based on our minimum
//        int minw = getPaddingLeft() + getPaddingRight() + getSuggestedMinimumWidth();
//
//        int w = Math.max(minw, MeasureSpec.getSize(widthMeasureSpec));
//
//        // Whatever the width ends up being, ask for a height that would let the pie
//        // get as big as it can
//        int minh = (w - (int) mTextWidth) + getPaddingBottom() + getPaddingTop();
//        int h = Math.min(MeasureSpec.getSize(heightMeasureSpec), minh);
//
//        setMeasuredDimension(w, h);
//    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        float xpad = (float) (getPaddingLeft() + getPaddingRight());
        float ypad = (float) (getPaddingTop() + getPaddingBottom());

        float ww = (float) w - xpad - axisYSpacing;
        float hh = (float) h - ypad - axisXSpacing;

        // Figure out how big we can make the pie.
        mPlotBounds = new RectF(0f, 0f, ww, hh);
        mPlotBounds.offsetTo(getPaddingLeft() + axisYSpacing, getPaddingTop());

        onDataChanged();
    }

    /**
     * Do all of the recalculations needed when the data array changes.
     */
    private void onDataChanged() {
    	if (mData.size() == 0) {
    		return;
    	}
    	xmin = mData.get(0).minx;
    	xmax = mData.get(0).maxx;
    	ymin = mData.get(0).miny;
    	ymax = mData.get(0).maxy;
        for (Seria it : mData) {
        	if (it.maxx > xmax) {
        		xmax = it.maxx;
        	}
        	if (it.maxy > ymax) {
        		ymax = it.maxy;
        	}
        	if (it.minx < xmin) {
        		xmin = it.minx;
        	}
        	if (it.miny < ymin) {
        		ymin = it.miny;
        	}
        }
    }

    /**
     * Initialize the control. This code is in a separate method so that it can be
     * called from both constructors.
     */
	private void init() {
        // Set up the paint for the pie slices
        mPlotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

        // In edit mode it's nice to have some demo data, so add that here.
        if (this.isInEditMode()) {
            plot(new float[] {-1,1,2}, new float[] {1,3,2}, "label1", Color.BLUE, 2, DASHED);
            plot(new float[] {0,1,2}, new float[] {4,-1,2}, "label2", Color.RED, 3, SOLID);
            plot(new float[] {-1, 4}, new float[] {0, 0}, "label2", Color.BLACK, 3, SOLID);
            xTicks = new float[] {0, 1, 2, 3};
        }
    }
	
	private void update() {
		invalidate();
		requestLayout();
	}
	
	private float getXMin() {
		if (xTicks != null) {
			return xTicks[0];
		}
		return xmin;
	}
	
	private float getXMax() {
		if (xTicks != null) {
			return xTicks[xTicks.length - 1];
		}
		return xmax;
	}
	
	private float getYMin() {
		if (yTicks != null) {
			return yTicks[0];
		}
		return ymin;
	}
	
	private float getYMax() {
		if (yTicks != null) {
			return yTicks[yTicks.length - 1];
		}
		return ymax;
	}
	
	private int calcX(float x) {
		RectF r = mPlotBounds;
		return Math.round(r.left + r.width() * (x - getXMin()) / (getXMax() - getXMin()));
	}
	
	private int calcY(float y) {
		RectF r = mPlotBounds;
		return Math.round(r.top + r.height() * (1 - (y - getYMin()) / (getYMax() - getYMin())));
	}
	
	@Override
    protected void onDraw(Canvas canvas) {
    	super.onDraw(canvas);
    	// draw border
    	mPlotPaint.setStyle(Paint.Style.STROKE);
        mPlotPaint.setColor(Color.BLACK);
        canvas.drawRect(mPlotBounds, mPlotPaint);
        // draw grid
        mPlotPaint.setColor(gridColor);
        // x grid
        if (xTicks != null) {
        	for (float tick: xTicks) {
        		int x = calcX(tick);
        		canvas.drawLine(x, mPlotBounds.bottom, x, mPlotBounds.top, mPlotPaint);
        	}
        }
        if (yTicks != null) {
        	for (float tick: yTicks) {
        		int y = calcY(tick);
        		canvas.drawLine(mPlotBounds.left, y, mPlotBounds.right, y, mPlotPaint);
        	}
        }
        /* *************************
        *  draw the data
        ***************************/
        for (Seria s : mData) {
            stroke(s, canvas);
        }
        
        // draw rectangles that hide out of bounds lines
        mPlotPaint.setStyle(Style.FILL);
        mPlotPaint.setColor(Color.WHITE);
        // left
        canvas.drawRect(0, 0, mPlotBounds.left, this.getBottom(), mPlotPaint);
        // bottom
        canvas.drawRect(0, mPlotBounds.bottom + 1, this.getRight(), this.getBottom(), mPlotPaint);
        // right
        canvas.drawRect(mPlotBounds.right + 1, 0, this.getRight(), this.getBottom(), mPlotPaint);
        // top
        canvas.drawRect(0, 0, this.getRight(), mPlotBounds.top, mPlotPaint);
        
        // x ticks
        mPlotPaint.setStyle(Paint.Style.STROKE);
        mPlotPaint.setTextAlign(Align.CENTER);
        mPlotPaint.setTextSize(xTickTextHeight);
        mPlotPaint.setColor(xTickColor);
        if (xTicks != null) {
        	for (float tick: xTicks) {
        		textFigure(xTickFormat.format(tick).toString(),
        				calcX(tick), mPlotBounds.bottom + xTickTextHeight, canvas);
        	}
        }
        mPlotPaint.setTextSize(labelTextHeight);
        mPlotPaint.setTextAlign(Align.RIGHT);
        if (labelX != null) {
        	textFigure(labelX, mPlotBounds.right, mPlotBounds.bottom + axisXSpacing, canvas);
        }
        
        // y ticks
        mPlotPaint.setTextAlign(Align.CENTER);
        mPlotPaint.setTextSize(yTickTextHeight);
        mPlotPaint.setColor(yTickColor);
        if (yTicks != null) {
        	for (float tick: yTicks) {
        		textFigure(yTickFormat.format(tick).toString(), 
        				mPlotBounds.left - yTickTextHeight, calcY(tick), 90, canvas);
        	}
        }
        mPlotPaint.setTextSize(labelTextHeight);
        mPlotPaint.setTextAlign(Align.RIGHT);
        if (labelX != null) {
        	textFigure(labelX, mPlotBounds.right, mPlotBounds.bottom + axisXSpacing, canvas);
        }
        mPlotPaint.setTextAlign(Align.LEFT);
        if (labelY != null) {
        	textFigure(labelY, mPlotBounds.left - axisYSpacing, mPlotBounds.top, 90, canvas);
        }
    }
	
	private void stroke(Seria s, Canvas canvas) {
		mPlotPaint.setStyle(Paint.Style.STROKE);
		// draw line
		if (s.mWidth > 0) {
			// create the path
	    	mPath.reset();
	        mPath.moveTo(calcX(s.x[0]), calcY(s.y[0]));
			for (int i = 1; i < s.sz(); i++) {
				mPath.lineTo(calcX(s.x[i]), calcY(s.y[i]));
			}
			// draw the path
			mPlotPaint.setColor(s.mColor);
			mPlotPaint.setStrokeWidth(s.mWidth);
			mPlotPaint.setPathEffect(s.mEffect);
	        canvas.drawPath(mPath, mPlotPaint);
		}
        
		// draw images seria
        if (s.bms != null) {
        	for (int i = 0; i < s.sz(); i++) {
        		Bitmap bm;
        		if (s.bms.length > 1) {
        			bm = s.bms[i];
        		} else {
        			bm = s.bms[0];
        		}
        		float left = calcX(s.x[i]);
        		float top = calcY(s.y[i]);
        		if (s.hAlign == HALIGN.CENTER) {
        			left -= bm.getWidth() / 2;
        		} else if (s.hAlign == HALIGN.RIGHT) {
        			left -= bm.getWidth();
        		}
        		if (s.vAlign == VALIGN.CENTER) {
        			top -= bm.getHeight() / 2;
        		} else if (s.vAlign == VALIGN.BOTTOM) {
        			top -= bm.getHeight();
        		}
        		canvas.drawBitmap(s.bms[i], left, top, mPlotPaint);
        	}
        }
        if (s.texts != null) {
        	Align align = Align.CENTER;
        	switch(s.hAlign) {
        	case LEFT:
        		align = Align.RIGHT;
        	case RIGHT:
        		align = Align.LEFT;
        	}
        		
        	mPlotPaint.setTextAlign(align);
        	mPlotPaint.setStrokeWidth(0);
        	mPlotPaint.setColor(s.mColor);
        	for (int i = 0; i < s.sz(); i++) {
        		float x = calcX(s.x[i]);
        		float y = calcY(s.y[i]);
        		switch(s.vAlign) {
            	case TOP:
            		y -= mPlotPaint.getTextSize();
            	case CENTER:
            		y -= 0.5 * mPlotPaint.getTextSize();
            	}
         		if (s.deg != 0) {
         			canvas.save();
         			canvas.rotate(s.deg, x, y);
         		}
             	canvas.drawText(s.texts[i], x, y, mPlotPaint);
             	if (s.deg != 0) {
             		canvas.restore();
             	}
        	}
        }
    }
}
